/**
 * Alipay.com Inc.
 * Copyright (c) 2004-2012 All Rights Reserved.
 */
package com.alipay.zdal.datasource.resource.adapter.jdbc;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import com.alipay.zdal.datasource.ZDataSource;

/**
 * A wrapper for a statement.
 *
 * @todo remove the org.jboss.ejb.plugins.cmp.jdbc.WrappedStatement dependency
 *
 * @author ����
 * @version $Id: WrappedStatement.java, v 0.1 2014-1-6 ����05:31:18 Exp $
 */
public class WrappedStatement implements Statement, StatementAccess {

    private final WrappedConnection lc;
    private final Statement         s;

    protected String                dataSourceName = "";

    /** The result sets */
    private HashMap                 resultSets;

    /** Whether we are closed */
    private boolean                 closed         = false;

    /** The state lock */
    private final Object            lock           = new Object();

    /**  ���ÿ����Ĕ���Դ*/
    protected ZDataSource           zdatasource    = null;

    public ZDataSource getZdatasource() {
        return zdatasource;
    }

    public void setZdatasource(ZDataSource zdatasource) {
        this.zdatasource = zdatasource;
    }

    /**
     * @param lc
     * @param s
     * @param zdatasource
     */
    public WrappedStatement(final WrappedConnection lc, Statement s, ZDataSource zdatasource) {
        this.lc = lc;
        this.s = s;
        this.zdatasource = zdatasource;
        lc.registerStatement(this);
    }

    /**
     * @param lc
     * @param s
     * @param dataSourceName
     * @param zdatasource
     */
    public WrappedStatement(final WrappedConnection lc, Statement s, String dataSourceName,
                            ZDataSource zdatasource) {
        this.lc = lc;
        this.s = s;
        this.dataSourceName = dataSourceName;
        this.zdatasource = zdatasource;
        lc.registerStatement(this);
    }

    public void close() throws SQLException {
        synchronized (lock) {
            if (closed) {
                return;
            }
            closed = true;
        }
        lc.unregisterStatement(this);
        internalClose();
    }

    /** 
     * @see java.sql.Statement#execute(java.lang.String)
     */
    public boolean execute(String sql) throws SQLException {
        return this.executeInternal(sql, -1, null, null);
    }

    /** 
     * @see java.sql.Statement#execute(java.lang.String, int)
     */
    public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
        return this.executeInternal(sql, autoGeneratedKeys, null, null);
    }

    /** 
     * @see java.sql.Statement#execute(java.lang.String, int[])
     */
    public boolean execute(String sql, int[] columnIndexes) throws SQLException {
        return this.executeInternal(sql, -1, columnIndexes, null);
    }

    /** 
     * @see java.sql.Statement#execute(java.lang.String, java.lang.String[])
     */
    public boolean execute(String sql, String[] columnNames) throws SQLException {
        return this.executeInternal(sql, -1, null, columnNames);
    }

    /**
     * 
     * @param sql
     * @param autoGeneratedKeys
     * @param columnIndexes
     * @param columnNames
     * @return
     * @throws SQLException
     */
    private boolean executeInternal(String sql, int autoGeneratedKeys, int[] columnIndexes,
                                    String[] columnNames) throws SQLException {

        checkTransaction();
        try {
            checkConfiguredQueryTimeout();
            if (autoGeneratedKeys == -1 && columnIndexes == null && columnNames == null) {
                return s.execute(sql);
            } else if (autoGeneratedKeys != -1) {
                return s.execute(sql, autoGeneratedKeys);
            } else if (columnIndexes != null) {
                return s.execute(sql, columnIndexes);
            } else {
                return s.execute(sql, columnNames);
            }
        } catch (Throwable t) {
            throw checkException(t);
        } finally {
        }
    }

    public Connection getConnection() throws SQLException {
        return lc;
    }

    public SQLWarning getWarnings() throws SQLException {
        checkState();
        try {
            return s.getWarnings();
        } catch (Throwable t) {
            throw checkException(t);
        }
    }

    public void clearWarnings() throws SQLException {
        checkState();
        try {
            s.clearWarnings();
        } catch (Throwable t) {
            throw checkException(t);
        }
    }

    /** 
     * @see java.sql.Statement#executeQuery(java.lang.String)
     */
    public ResultSet executeQuery(String sql) throws SQLException {
        checkTransaction();
        try {
            checkConfiguredQueryTimeout();
            ResultSet result = s.executeQuery(sql);
            return registerResultSet(result);
        } catch (Throwable t) {
            throw checkException(t);
        } finally {
        }
    }

    /** 
     * @see java.sql.Statement#executeUpdate(java.lang.String)
     */
    public int executeUpdate(String sql) throws SQLException {
        return this.executeUpdateInternal(sql, -1, null, null);
    }

    /** 
     * @see java.sql.Statement#executeUpdate(java.lang.String, int)
     */
    public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
        return this.executeUpdateInternal(sql, autoGeneratedKeys, null, null);
    }

    /** 
     * @see java.sql.Statement#executeUpdate(java.lang.String, int[])
     */
    public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
        return this.executeUpdateInternal(sql, -1, columnIndexes, null);
    }

    /** 
     * @see java.sql.Statement#executeUpdate(java.lang.String, java.lang.String[])
     */
    public int executeUpdate(String sql, String[] columnNames) throws SQLException {
        return this.executeUpdateInternal(sql, -1, null, columnNames);
    }

    /**
     * 
     * @param sql
     * @param autoGeneratedKeys
     * @param columnIndexes
     * @param columnNames
     * @return
     * @throws SQLException
     */
    private int executeUpdateInternal(String sql, int autoGeneratedKeys, int[] columnIndexes,
                                      String[] columnNames) throws SQLException {
        checkTransaction();
        try {
            checkConfiguredQueryTimeout();

            if (autoGeneratedKeys == -1 && columnIndexes == null && columnNames == null) {
                return s.executeUpdate(sql);
            } else if (autoGeneratedKeys != -1) {
                return s.executeUpdate(sql, autoGeneratedKeys);
            } else if (columnIndexes != null) {
                return s.executeUpdate(sql, columnIndexes);
            } else {
                return s.executeUpdate(sql, columnNames);
            }
        } catch (Throwable t) {
            throw checkException(t);
        } finally {
        }
    }

    public int getMaxFieldSize() throws SQLException {
        checkState();
        try {
            return s.getMaxFieldSize();
        } catch (Throwable t) {
            throw checkException(t);
        }
    }

    public void setMaxFieldSize(int max) throws SQLException {
        checkState();
        try {
            s.setMaxFieldSize(max);
        } catch (Throwable t) {
            throw checkException(t);
        }
    }

    public int getMaxRows() throws SQLException {
        checkState();
        try {
            return s.getMaxRows();
        } catch (Throwable t) {
            throw checkException(t);
        }
    }

    public void setMaxRows(int max) throws SQLException {
        checkState();
        try {
            s.setMaxRows(max);
        } catch (Throwable t) {
            throw checkException(t);
        }
    }

    public void setEscapeProcessing(boolean enable) throws SQLException {
        checkState();
        try {
            s.setEscapeProcessing(enable);
        } catch (Throwable t) {
            throw checkException(t);
        }
    }

    public int getQueryTimeout() throws SQLException {
        checkState();
        try {
            return s.getQueryTimeout();
        } catch (Throwable t) {
            throw checkException(t);
        }
    }

    public void setQueryTimeout(int timeout) throws SQLException {
        checkState();
        try {
            s.setQueryTimeout(timeout);
        } catch (Throwable t) {
            throw checkException(t);
        }
    }

    public void cancel() throws SQLException {
        checkState();
        try {
            s.cancel();
        } catch (Throwable t) {
            throw checkException(t);
        }
    }

    public void setCursorName(String name) throws SQLException {
        checkState();
        try {
            s.setCursorName(name);
        } catch (Throwable t) {
            throw checkException(t);
        }
    }

    public ResultSet getResultSet() throws SQLException {
        checkState();
        try {
            ResultSet result = s.getResultSet();
            if (result == null) {
                return null;
            } else {
                return registerResultSet(result);
            }
        } catch (Throwable t) {
            throw checkException(t);
        }
    }

    public int getUpdateCount() throws SQLException {
        checkState();
        try {
            return s.getUpdateCount();
        } catch (Throwable t) {
            throw checkException(t);
        }
    }

    public boolean getMoreResults() throws SQLException {
        checkState();
        try {
            return s.getMoreResults();
        } catch (Throwable t) {
            throw checkException(t);
        }
    }

    public boolean getMoreResults(int current) throws SQLException {
        checkState();
        try {
            return s.getMoreResults(current);
        } catch (Throwable t) {
            throw checkException(t);
        }
    }

    public void setFetchDirection(int direction) throws SQLException {
        checkState();
        try {
            s.setFetchDirection(direction);
        } catch (Throwable t) {
            throw checkException(t);
        }
    }

    public int getFetchDirection() throws SQLException {
        checkState();
        try {
            return s.getFetchDirection();
        } catch (Throwable t) {
            throw checkException(t);
        }
    }

    public void setFetchSize(int rows) throws SQLException {
        checkState();
        try {
            s.setFetchSize(rows);
        } catch (Throwable t) {
            throw checkException(t);
        }
    }

    public int getFetchSize() throws SQLException {
        checkState();
        try {
            return s.getFetchSize();
        } catch (Throwable t) {
            throw checkException(t);
        }
    }

    public int getResultSetConcurrency() throws SQLException {
        checkState();
        try {
            return s.getResultSetConcurrency();
        } catch (Throwable t) {
            throw checkException(t);
        }
    }

    public int getResultSetType() throws SQLException {
        checkState();
        try {
            return s.getResultSetType();
        } catch (Throwable t) {
            throw checkException(t);
        }
    }

    /** 
     * @see java.sql.Statement#addBatch(java.lang.String)
     */
    public void addBatch(String sql) throws SQLException {

        //  doSqlValve(sql);
        checkState();
        try {
            s.addBatch(sql);
        } catch (Throwable t) {
            throw checkException(t);
        } finally {
        }
    }

    /** 
     * @see java.sql.Statement#clearBatch()
     */
    public void clearBatch() throws SQLException {
        checkState();
        try {
            s.clearBatch();
        } catch (Throwable t) {
            throw checkException(t);
        }
    }

    /** 
     * @see java.sql.Statement#executeBatch()
     */
    public int[] executeBatch() throws SQLException {
        checkState();
        try {
            checkConfiguredQueryTimeout();
            return s.executeBatch();
        } catch (Throwable t) {
            throw checkException(t);
        }
    }

    public ResultSet getGeneratedKeys() throws SQLException {
        checkState();
        try {
            ResultSet resultSet = s.getGeneratedKeys();
            return registerResultSet(resultSet);
        } catch (Throwable t) {
            throw checkException(t);
        }
    }

    public int getResultSetHoldability() throws SQLException {
        checkState();
        try {
            return s.getResultSetHoldability();
        } catch (Throwable t) {
            throw checkException(t);
        }
    }

    public Statement getUnderlyingStatement() throws SQLException {
        checkState();
        return s;
    }

    protected SQLException checkException(Throwable t) throws SQLException {
        throw lc.checkException(t);
    }

    protected void checkTransaction() throws SQLException {
        checkState();
        lc.checkTransaction();
    }

    protected void checkConfiguredQueryTimeout() throws SQLException {
        lc.checkConfiguredQueryTimeout(this);
    }

    protected void internalClose() throws SQLException {
        synchronized (lock) {
            closed = true;
        }
        try {
            closeResultSets();
        } finally {
            s.close();
        }
    }

    /**
     * 
     * @throws SQLException
     */
    void checkState() throws SQLException {
        synchronized (lock) {
            if (closed) {
                throw new SQLException("The statement is closed.");
            }
        }
    }

    /**
     * 
     * @param resultSet
     * @return
     */
    protected ResultSet registerResultSet(ResultSet resultSet) {
        if (resultSet != null) {
            resultSet = new WrappedResultSet(this, resultSet);
        }

        if (lc.getTrackStatements() == BaseWrapperManagedConnectionFactory.TRACK_STATEMENTS_FALSE_INT) {
            return resultSet;
        }

        synchronized (this) {
            if (resultSets == null) {
                resultSets = new HashMap();
            }
            if (lc.getTrackStatements() == BaseWrapperManagedConnectionFactory.TRACK_STATEMENTS_TRUE_INT) {
                resultSets.put(resultSet, new Throwable("STACKTRACE"));
            } else {
                resultSets.put(resultSet, null);
            }
        }
        return resultSet;
    }

    /**
     * 
     * @param resultSet
     */
    protected void unregisterResultSet(WrappedResultSet resultSet) {
        if (lc.getTrackStatements() == BaseWrapperManagedConnectionFactory.TRACK_STATEMENTS_FALSE_INT) {
            return;
        }

        synchronized (this) {
            if (resultSets != null) {
                resultSets.remove(resultSet);
            }
        }
    }

    /**
     * 
     */
    protected void closeResultSets() {
        if (lc.getTrackStatements() == BaseWrapperManagedConnectionFactory.TRACK_STATEMENTS_FALSE_INT) {
            return;
        }

        synchronized (this) {
            if (resultSets == null) {
                return;
            }

            for (Iterator i = resultSets.entrySet().iterator(); i.hasNext();) {
                Map.Entry entry = (Map.Entry) i.next();
                WrappedResultSet resultSet = (WrappedResultSet) entry.getKey();
                if (lc.getTrackStatements() == BaseWrapperManagedConnectionFactory.TRACK_STATEMENTS_TRUE_INT) {
                    Throwable stackTrace = (Throwable) entry.getValue();
                    lc.getLogger()
                        .warn("Closing a result set you left open! Please close it yourself.",
                            stackTrace);
                }
                try {
                    resultSet.internalClose();
                } catch (Throwable t) {
                    lc.getLogger().warn(
                        "Error closing a result set you left open! Please close it yourself.", t);
                }
            }
            resultSets.clear();
        }
    }

    // jdk 6
    public boolean isClosed() throws SQLException {
        return closed;
    }

    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return iface.isInstance(s);
    }

    public <T> T unwrap(Class<T> iface) throws SQLException {
        return (T) (iface.isInstance(s) ? s : null);
    }

    public boolean isPoolable() throws SQLException {
        return false;
    }

    public void setPoolable(boolean poolable) throws SQLException {
    }

    protected String getDataSourceName() {
        return dataSourceName;
    }

    protected void setDataSourceName(String dataSourceName) {
        this.dataSourceName = dataSourceName;
    }

	@Override
	public void closeOnCompletion() throws SQLException {
		
	}

	@Override
	public boolean isCloseOnCompletion() throws SQLException {
		return false;
	}

}
